#include <unistd.h>
#include <file.h>
#include <math.h>
#define NEW_BINARY_FILE 0
#define OLD_BINARY_FILE 1

// returns the current length of the file, in pages
int File:: GetLength ()
{
	// how should we implement this function? 
	// option1: keep the number of pages in the file, in the first 4 bytes.
		// adv: just one look up.
		// disadv: need to write it every time we add a page or delete a page
	// option2: do repeated lseeks to get the file size;
		// adv: stateless! no need of updates.
		// disadv: repeated lseeks?
	// option3: system call for size, and then divide by page size?
	
	if(m_iFd < 0)
	{
		cout<<"File Not Open!";
		abort();
	}

	
	long iFileSize= lseek(m_iFd,0,SEEK_END);
	double dPage = ceil(iFileSize / PAGESIZE);

	return (int)dPage;
		
}

// opens the given file
// the first parameter tells whether or not to
// create the file.  If the parameter is zero, a new file is created
// the file if notNew is zero, then the file is created and any other
// file located at that location is erased.  Otherwise, the file is
// simply opened

void File:: Open (int notNew, char *fName)
{
	if(m_iFd >= 0)
	{
		cout<<"Bad Donought:Cannot open the file again!!";
		abort();
	}
	
	assert(fName);

	if(strlen(fName) == 0)
	{
		cout<< "Empty Filename in open call"<<endl;
		abort();
	}

	if(m_cBinaryFName == NULL)
	{
		m_cBinaryFName = new char[strlen(fName)+1];
		strcpy(m_cBinaryFName,fName);
	}
	else
	{
		delete [] m_cBinaryFName;
		m_cBinaryFName = new char[strlen(fName)+1];
		strcpy(m_cBinaryFName,fName);
	}

	//cout<<"m_cBinaryFName"<< m_cBinaryFName <<endl;
	
	
	if(notNew == 0)
	{
		//system(strcat("rm -f ",fName));
	
		m_iFd = open(fName,O_RDWR|O_CREAT|O_TRUNC,S_IRWXU);
		
		if(m_iFd<0)
		{
			cout<< "Create Failed";
			abort();
		}
	}
	else
	{
		m_iFd = open(fName,O_RDWR,S_IRWXU);
		
		if(m_iFd < 0)
		{
			cout<< "Open Failed";
			abort();
		}
	}

	//cout << "m_iFd:"<<m_iFd<<endl;

	m_iCurrPageNum = 1;
	m_iCurrRec = 0;
}

// moves the record "pointer" in the file to the first record on
// the specified page in the file


void File:: MovePointer(int _whichPage)
{
	int iOffset = (_whichPage-1) * PAGESIZE ;
	
	if(m_iFd < 0)
	{
		cout<< "Lseek: cannot seek on a closed file ";
			
	}	
	lseek(m_iFd,iOffset,SEEK_SET);	

	m_iCurrPageNum = _whichPage;
	m_iCurrRec = 0;
	
	if(m_pCurrentPage)
		m_pCurrentPage->EmptyItOut();
}

// gets the next record in the file
// returns a 0 if there are no more
// records and a 1 otherwise

int File:: NextRec (Record &getMe)
{
	
	if(m_pCurrentPage == NULL)
	{
		m_pCurrentPage = new Page(PAGESIZE);
		MovePointer(m_iCurrPageNum);
		GetPage(*m_pCurrentPage, m_iCurrPageNum);
	}	
	
	int iNumPages = GetLength();

	while(true)
	{
		int retStatus = m_pCurrentPage->GetFirst(getMe);
		
		if(retStatus == 0)
		{			
			//page empty case
			m_pCurrentPage->EmptyItOut();
			
			if((m_iCurrPageNum+1) <= iNumPages)
				GetPage(*m_pCurrentPage, ++m_iCurrPageNum);
			else
			{
			   //MovePointer(m_iCurrPageNum);
			   return 0;
			}

		}
		else 
		{
			m_iCurrRec++;
			return 1;
		}
	}

}

// adds a new record to the very end of the file
// this also updates
// the current "pointer" so that it is pointing just past the end of the file

void File:: Append (Record &addMe)
{
	int iNumPages = GetLength();
	Page oTmpPage(PAGESIZE);

	//Go to the lastPage.
	GetPage(oTmpPage, iNumPages);
	
	int iRet = oTmpPage.Append(addMe);

	if(iRet == 0)
	{
		//page is full
		oTmpPage.EmptyItOut();
		oTmpPage.Append(addMe);
		AddPage(oTmpPage, iNumPages +1);	
	}
	else
	{
		AddPage(oTmpPage, iNumPages);	
	}
}

// allows someone to explicitly get a specified page from the file

void File:: GetPage (Page &putItHere, int whichPage)
{
	
	if(m_iFd < 0)
        {
                cout<< "Error getting page: file not open"<<endl;
                abort();
        }

	MovePointer(whichPage);	
	assert(m_cReadPad);
	int iRetStatus = read(m_iFd,m_cReadPad,PAGESIZE);	
	
	putItHere.FromBinary(m_cReadPad);
}

// allows someone to explicitly write a specified page to the file
// if the write is past the end of the file, all of the new pages that
// are before the page to be written are zeroed out

void File:: AddPage (Page &addMe, int whichPage)
{

	assert(m_cScratchPad);

	if(m_iFd < 0)
	{
		cout<< "Error adding page: file not open"<<endl;
		abort();
	}

	memset(m_cScratchPad, '\0',PAGESIZE);

	int iFileSize = GetLength();

	if(whichPage > iFileSize)
	{
		for(int i=iFileSize+1; i<whichPage ; i++) // zero out all pages between last and whichPage
		{
			MovePointer(i);
			if(write(m_iFd,m_cScratchPad,PAGESIZE) < 0)
			{
				cout<< "Error Writing page: Unknown error"<<endl;
		                abort();
			}
		}
	}

	
	addMe.ToBinary(m_cScratchPad);
	MovePointer(whichPage);

	int iRetStatus = write(m_iFd,m_cScratchPad,PAGESIZE);		
	
	if(iRetStatus < 0)
	{
		cout<< "Error Writing page: Unknown error"<<endl;
                abort();
	}
	//cout<< "page written correctly";
}

// closes the file and returns the file length (in number of pages)

int File:: Close ()
{
	if(m_iFd < 0)
	{
		cout<< "Error while closing the file";
		abort();
	}
	else
	{
		close(m_iFd);
		m_iFd = -1;
		if(m_cBinaryFName)
			delete [] m_cBinaryFName;
	}
}

File:: File ()
{
	m_cScratchPad = new char[PAGESIZE];
	memset(m_cScratchPad, '\0',PAGESIZE);

	m_cReadPad= new char[PAGESIZE];
	memset(m_cReadPad, '\0',PAGESIZE);

	m_bPageDirty = false;
	m_cBinaryFName = NULL;
	m_iFd = -1;
	m_iCurrPageNum = 0;
	m_iCurrRec = 0;
	
	m_pCurrentPage = NULL;
}

File:: ~File ()
{
//	if(m_cBinaryFName)
//		delete [] m_cBinaryFName;
	m_bPageDirty = false;
        m_cBinaryFName = NULL;
        m_iFd = -1;
	if(m_cScratchPad)
		delete [] m_cScratchPad;

	if(m_cReadPad)
		delete [] m_cReadPad;

	m_pCurrentPage = NULL;
}


void File:: setParams(char* _cTxtName,char* _cSchName,char* _cRelName,char* _cBinPath)
{

	// start error checks: I must admit that this is the worlds stupidest method of checking NULL
	// I am gng to use exception re throwing here, with multi level custom errors that will be based on the project you are doing
	// By this rant I mean: all error messages in project 1 will be appended by P1. this would locate the error code once we come to more complex data structures like B+ trees etc. 
	// just a matter of time.. till then, this stupid thing is the king!! 
	// ~Nikhil.

	assert(_cTxtName);
	assert(_cSchName);
	assert(_cRelName);
	assert(_cBinPath);

	m_cBinaryFName = new char[strlen(_cBinPath)+1];	
	assert(m_cBinaryFName);
	strcpy(m_cBinaryFName,_cBinPath);

	m_cTextFName = new char[strlen(_cTxtName)+1];
	assert(m_cTextFName);
	strcpy(m_cTextFName,_cTxtName);
			
	m_cSchName= new char[strlen(_cSchName)+1];
	assert(m_cSchName);
	strcpy(m_cSchName,_cSchName);

	m_cRelName = new char[strlen(_cRelName)+1];
	assert(m_cRelName);
	strcpy(m_cRelName,_cRelName);

	// error checks end.
}

int File:: txt2bin()
{

	FILE *pFile=NULL;
	
	// open txt file in read mode.
	pFile = fopen (m_cTextFName, "r");

	// check that file was opened.	
	assert(pFile);

	// create a schema object for suck next rec.
	Schema oSchema(m_cRelName, m_cSchName);

	// create a page to store the records	
	Page pTmpPage(PAGESIZE);
	char* cTmpStr = new char[strlen(m_cBinaryFName) +1];
	strcpy(cTmpStr,m_cBinaryFName);

	// open new binary file.
	Open(NEW_BINARY_FILE,cTmpStr);	

	Record oRec;
	int iRecRcvd = 0;
	int iRecAppend = 0;
	int iPageNumber = 0;
	m_cScratchPad = new char[PAGESIZE];
	int offset = 0;
	
	
	while(1)
	{
		int counter = 0;	
		do
		{
			//Record R;
			iRecRcvd = oRec.SuckNextRecord(oSchema, pFile);
			//cout<< "suckNext:: "<< iRecRcvd<<endl;
			//R.Copy(oRec);
			
			if(iRecRcvd == 1)
				iRecAppend = pTmpPage.Append(oRec);
				

			counter++;
		}while(iRecRcvd==1 && iRecAppend== 1);

		//cout<<"Number of records added="<<counter-1<<endl;

		//cout<<"Adding Page"<<endl;
		offset = iPageNumber * PAGESIZE;
		lseek(m_iFd,offset,SEEK_SET);
		pTmpPage.ToBinary(m_cScratchPad);
		iPageNumber++;
		if(write(m_iFd,m_cScratchPad,PAGESIZE) < 0)
		{
			cout<< "Error Writing page: Unknown error"<<endl;
			abort();
		}


		if(iRecRcvd == 1 && iRecAppend == 0)
		{
			// rec received but page full.
			//Record R1;
			pTmpPage.EmptyItOut();
			//R1.Copy(oRec);
			pTmpPage.Append(oRec);
		}

		if(iRecRcvd == 0)
			break;
	}	
	delete [] cTmpStr;

	Close();
	fclose(pFile);
	return 0;
}
void File:: ApplyPredicate(char* _cSchFile, char* _cRelSchema, char* _cPredName, char* _cBinName)
{
	assert(_cSchFile);
	assert(_cRelSchema);
	assert(_cPredName);
	assert(_cBinName);
	
	Schema oSchema(_cRelSchema, _cSchFile);
	//oSchema.Print();
	Predicate pred(oSchema, _cPredName);
	//pred.Print(oSchema);
	//cout << _cSchFile << " " << _cRelSchema << " " << _cPredName << " " << _cBinName << endl;
	Open(OLD_BINARY_FILE,_cBinName);
	MovePointer(1);
	
	int retNextRec = 1;
	int goodPred   = 1;
	int count      = 0;	
	oSchema.Print();
	//pred.Print(oSchema);
	//Record rec;
	while(retNextRec)
	{
		Record rec;
		retNextRec = NextRec (rec);
		if(retNextRec == 1)
		{
			//rec.Print(oSchema);
			goodPred = pred.Apply(rec);
		
			if(goodPred == 1)
			{
				rec.Print(oSchema);
			}
			
			count++;
		}
		rec.SetFree();
	}
	Close();
}

